package org.munoz.shared;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

import com.google.gwt.core.shared.GWT;

public class State {
	public static final int MAX_NUM_PLAYERS = 2; // Only two players allowed for now.
	public final static int PLAYER1 = 0;
	public final static int PLAYER2 = 1;
	public static final int TIE = 3;
	private int turn; // 0 = player 1, 1 = player 2. 
	private Deck toDeal;
//	private int numPlayers;
	private Player[] players;
	private ArrayList<Rank> fourRankCardsPlacedInTable; // = new int[13]; 
	private GameResult gameResult;
	
	// Create initial state 
	public State(){
		this.toDeal = new Deck();//DeckOld.createShuffledDeck();
//		toDeal.restoreDeck();
//		IntGeneratorJP gen = new IntGeneratorJP();
//		toDeal.shuffle(gen);
		this.turn = PLAYER1;
 //		this.numPlayers = plays.length;
		players = new Player[MAX_NUM_PLAYERS];
		players[State.PLAYER1] = new Player(State.PLAYER1);
		players[State.PLAYER2] = new Player(State.PLAYER2);		
		this.fourRankCardsPlacedInTable = new ArrayList<Rank>();
		//players = plays;
	}	
	
	public State(int turn, Deck mainDeck, Player player1, Player player2, GameResult gameResult, ArrayList<Rank> placedInTable){
		this.turn = turn;
		this.gameResult = gameResult;
		this.players = new Player[2];
		this.players[PLAYER1] = player1;
		this.players[PLAYER2] = player2;
		this.toDeal = mainDeck;
		this.fourRankCardsPlacedInTable = placedInTable;
	}
	
	/**
	 * @return a copy of the state
	 */
	public State copy(){
		ArrayList<Rank> copyInTable = new ArrayList<Rank>();
		for(int i = 0; i<fourRankCardsPlacedInTable.size(); i++){
			copyInTable.add(fourRankCardsPlacedInTable.get(i));
		}
		GameResult copyGR = null; 
		if(gameResult != null)
			copyGR = gameResult.getCopy();			
		return new State(turn, toDeal.getDeckCopy(), players[PLAYER1].getPlayerCopy(), players[PLAYER2].getPlayerCopy(), 
				copyGR, copyInTable);
	}

	
	/**
	 * @return the size of the deck
	 */
	public int getSizeOfDeck(){
		return toDeal.getNumberOfCardsRemaining();
	}

	/**
	 * @return the player that must play
	 */
	public int getTurn() {
		return turn;
	}

	/**
	 * Sets next player to move
	 * @param turn 
	 */
	public void setTurn(int turn) {
		this.turn = turn;
	}

	/**
	 * @return the deck in the table
	 */
	public Deck getToDeal() {
		return toDeal;
	}

	/**
	 * Sets the deck in the table.
	 * @param toDeal
	 */
	public void setToDeal(Deck toDeal) {
		this.toDeal = toDeal;
	}

	/**
	 * @return the players of the game
	 */
	public Player[] getPlayers() {
		return players;
	}

	/**
	 * Set the players of the game
	 * @param players
	 */
	public void setPlayers(Player[] players) {
		this.players = players;
	}

	public ArrayList<Rank> getFourRankCardsPlacedInTable() {
		return fourRankCardsPlacedInTable;
	}

	public void setFourRankCardsPlacedInTable(ArrayList<Rank> fourRankCardsPlacedInTable) {
		this.fourRankCardsPlacedInTable = fourRankCardsPlacedInTable;
	}

	public GameResult getGameResult() {
		return gameResult;
	}

	public void setGameResult(GameResult gameResult) {
		this.gameResult = gameResult;
	}
	
	public boolean isAllCardsSameRankInTable(Rank r){
		for(int i=0; i<fourRankCardsPlacedInTable.size(); i++){
			if(fourRankCardsPlacedInTable.get(i) == r){
				return true;
			}
		}
		return false;
	}

	public void dealInitialHands(int numCardsPerPlayer) {
		for(int i=0; i<numCardsPerPlayer; i++){
			players[0].getHand().addOneCard(toDeal.dealCard());
			players[1].getHand().addOneCard(toDeal.dealCard());			
		}
	}
	
	@Override
	public boolean equals(Object obj){
		if(this == obj) return true;
		if(obj == null) return false;
		if(!(obj instanceof State)) return false;
		State other = (State)obj;
		// Comparare Turn, toDeal, players, fourCardsPlacedInTable, gameResult 
		// 
		return this.turn == other.turn 
				&& toDeal.getCards().equals(other.toDeal.getCards())
//				// TODO. For now, I am only checking size but fix how to check hands in player. It is not that 
				// easy because we are removing from an index and inserting in the head. 
//				&& players[PLAYER1].getHand().getCards().equals(other.players[PLAYER1].getHand().getCards())
//				&& players[PLAYER2].getHand().getCards().equals(other.players[PLAYER2].getHand().getCards())
				// For now, check size; 
				&& players[PLAYER1].getNumberOfCardsInHand() == other.players[PLAYER1].getNumberOfCardsInHand()
				&& players[PLAYER2].getNumberOfCardsInHand() == other.players[PLAYER2].getNumberOfCardsInHand()
				&& fourRankCardsPlacedInTable.equals(other.fourRankCardsPlacedInTable)
				&& ((gameResult == null && other.gameResult == null) || gameResult.equals(other.gameResult))
				;			
	}
	
	@Override
	public String toString() {
		return "State [turn=" + turn + ", toDeal=" + toDeal + ", players="
				+ Arrays.toString(players) + ", fourRankCardsPlacedInTable="
				+ fourRankCardsPlacedInTable.toString() + ", gameResult="
				+ gameResult + "]";
	}

	/**
     * 
     * Serializes the state object and returns a String that can be used as a token for history support. 
     * @param state
     * @return
     */
    public static String serializeState(State state) {
    	// Turn
    	String serialized = state.getTurn() + "_";
    	// Deck
    	serialized += "d_"; // deck 
    	for(Card c : state.getToDeal().getCards()){
    		serialized += getCardInt(c) + "_";//c.getSuit().toString() + "_" + c.getRank().toString() + "_";
    	}
    	// Player 1 Hand
    	serialized += "p1_"; 
    	for(Card c : state.getPlayers()[State.PLAYER1].getHand().getCards()){
    		serialized += getCardInt(c) + "_"; //c.getSuit().toString() + "_" + c.getRank().toString() + "_";
    	}
    	serialized += "tP1_"; // player 1 cards in table
    	for(Rank r : state.getPlayers()[State.PLAYER1].getPlayerFourRankCardsPlacedInTable()){
    		serialized += r.ordinal() + "_";//r.toString() + "_";
    	}
    	// Player 2 Hand
    	serialized += "p2_"; 
    	for(Card c : state.getPlayers()[State.PLAYER2].getHand().getCards()){
    		serialized += getCardInt(c) + "_"; // c.getSuit().toString() + "_" + c.getRank().toString() + "_";
    	}
    	serialized += "tP2_"; 
    	for(Rank r : state.getPlayers()[State.PLAYER2].getPlayerFourRankCardsPlacedInTable()){
    		serialized += r.ordinal() + "_";//r.toString() + "_";
    	}
    	// Game Result;
    	serialized += "gR_";  // Game Result
    	if(state.getGameResult() != null){
    		serialized += state.getGameResult().getWinner() + "_";
    		serialized += state.getGameResult().getGameResultReason().toString() + "_";
    	}
    	else{
    		serialized += "n_";
    	}
    	return serialized;
    }
    
    /**
     * It takes a String with the proper format and returns a State object with the features described in the string.  
     * @param serialized
     * @return
     */
    public static State unserializeState(String serialized) {
    	System.out.println("In unserializeState()");
    	try{
    		if(serialized.length()==0){
    			return new State();
    		}
    		String [] tokens = serialized.split("[_]");
    		int turn = Integer.parseInt(tokens[0]);
        	System.out.println("In unserializeState() User turn " + turn);
    		int i = 1;
    		Deck deck = new Deck();
    		if(tokens[i].compareTo("d") != 0){ // deck
    			return new State();
    		}
    		i++;
    		// Getting cards for main Deck. 
    		while(tokens[i].compareTo("p1") != 0){
//    			Suit s = getSuitFromString(tokens[i]);
//    			if(s == null){
//    				return new State();
//    			}
//    			i++;
//    			Rank r = getRankFromString(tokens[i]);
//    			if(r == null){
//    				return new State();
//    			}
//    			i++;
//    			Card c = new Card(s, r);
    			Card c = getCardFromInt(Integer.parseInt(tokens[i]));
    			deck.addOneCard(c);
    			i++;
    		}
        	System.out.println("In unserializeState() Got player 1 cards");
    		i++;
    		// Getting player 1's hand data
    		Player p1 = new Player(State.PLAYER1);
    		while(tokens[i].compareTo("tP1")!=0){
//    			Suit s = getSuitFromString(tokens[i]);
//    			if(s == null){
//    				return new State();
//    			}
//    			i++;
//    			Rank r = getRankFromString(tokens[i]);
//    			if(r == null){
//    				return new State();
//    			}
//    			i++;
//    			Card c = new Card(s, r);
    			Card c = getCardFromInt(Integer.parseInt(tokens[i]));
    			p1.getHand().addOneCard(c);
    			i++;
    		}
    		// Move head.
    		while(tokens[i].compareTo("tP1")!=0){
    			i++;
    		}
    		i++;
    		// Getting player 1's cardsInTable
    		while(tokens[i].compareTo("p2")!=0){
//    			Rank r = getRankFromString(tokens[i]);
//    			if(r == null){
//    				return new State();
//    			}
    			Rank r = Rank.values()[Integer.parseInt(tokens[i])];
    			i++;
    			p1.getPlayerFourRankCardsPlacedInTable().add(r);
    		}
    		i++;
    		// Getting player 2's hand data
    		Player p2 = new Player(State.PLAYER2);
    		while(tokens[i].compareTo("tP2")!=0){
//    			Suit s = getSuitFromString(tokens[i]);
//    			if(s == null){
//    				return new State();
//    			}
//    			i++;
//    			Rank r = getRankFromString(tokens[i]);
//    			if(r == null){
//    				return new State();
//    			}
//    			i++;
//    			Card c = new Card(s, r);
    			Card c = getCardFromInt(Integer.parseInt(tokens[i]));
    			p2.getHand().addOneCard(c);
    			i++;
    		}
    		i++;
    		// Getting player 2's cardsInTable
    		while(tokens[i].compareTo("gR")!=0){
//    			Rank r = getRankFromString(tokens[i]);
//    			if(r == null){
//    				return new State();
//    			}
//    			i++;
    			Rank r = Rank.values()[Integer.parseInt(tokens[i])];
    			i++;
    			p2.getPlayerFourRankCardsPlacedInTable().add(r);
    		}
    		i++;
    		// Getting game's result.
    		GameResult gameResult = null;
    		if(tokens[i].compareTo("n")!=0){
    			int winner = Integer.parseInt(tokens[i]);
    			i++;
    			GameResultReason reason = getReasonFromString(tokens[i]);
    			gameResult = new GameResult(winner, reason);
    			i++;
    		}
    		ArrayList<Rank> cardsPlacedInTable = new ArrayList<Rank>();
    		for(Rank r :p1.getPlayerFourRankCardsPlacedInTable()){
    			cardsPlacedInTable.add(r);
    		}
    		for(Rank r :p2.getPlayerFourRankCardsPlacedInTable()){
    			cardsPlacedInTable.add(r);
    		}
        	System.out.println("In unserializeState() About to return state");
        	return new State(turn, deck, p1, p2, gameResult, cardsPlacedInTable);
    	}
    	catch(StringIndexOutOfBoundsException e){
    		GWT.log("String error unserializing the state");
    		return new State();
    	}
    	catch(ArrayIndexOutOfBoundsException e){
    		GWT.log("Array error unserializing the state");
    		return new State();
    	}

    }
    
    private static int getCardInt(Card c){
    	int rep = c.getSuit().ordinal()*13 + c.getRank().ordinal(); 
//    	System.out.println(rep + " " + c.toString());
    	return rep;
    }
    
    private static Card getCardFromInt(int i){
    	Suit s = Suit.values()[(int) Math.floor(i/13)];
    	Rank r = Rank.values()[(i%13)];
    	Card c = new Card(s, r);
//    	System.out.println(c.toString() + " " + i);
    	return c;
    }
    
    /**
     * 
     * Serializes the state object and returns a String that can be used as a token for history support. 
     * @param state
     * @return
     */
    public static String OLDserializeState(State state) {
    	// Turn
    	String serialized = state.getTurn() + "_";
    	// Deck
    	serialized += "deck_"; 
    	for(Card c : state.getToDeal().getCards()){
    		serialized += c.getSuit().toString() + "_" + c.getRank().toString() + "_";
    	}
    	// Player 1 Hand
    	serialized += "player1_"; 
    	for(Card c : state.getPlayers()[State.PLAYER1].getHand().getCards()){
    		serialized += c.getSuit().toString() + "_" + c.getRank().toString() + "_";
    	}
    	serialized += "inTableP1_";
    	for(Rank r : state.getPlayers()[State.PLAYER1].getPlayerFourRankCardsPlacedInTable()){
    		serialized += r.toString() + "_";
    	}
    	// Player 2 Hand
    	serialized += "player2_"; 
    	for(Card c : state.getPlayers()[State.PLAYER2].getHand().getCards()){
    		serialized += c.getSuit().toString() + "_" + c.getRank().toString() + "_";
    	}
    	serialized += "inTableP2_"; 
    	for(Rank r : state.getPlayers()[State.PLAYER2].getPlayerFourRankCardsPlacedInTable()){
    		serialized += r.toString() + "_";
    	}
    	// Game Result;
    	serialized += "gameResult_"; 
    	if(state.getGameResult() != null){
    		serialized += state.getGameResult().getWinner() + "_";
    		serialized += state.getGameResult().getGameResultReason().toString() + "_";
    	}
    	else{
    		serialized += "null_";
    	}
    	return serialized;
    }
    
    /**
     * It takes a String with the proper format and returns a State object with the features described in the string.  
     * @param serialized
     * @return
     */
    public static State OLDunserializeState(String serialized) {
    	try{
    		if(serialized.length()==0){
    			return new State();
    		}
    		String [] tokens = serialized.split("[_]");
    		int turn = Integer.parseInt(tokens[0]);
    		int i = 1;
    		Deck deck = new Deck();
    		if(tokens[i].compareTo("deck") != 0){
    			return new State();
    		}
    		i++;
    		// Getting cards for main Deck. 
    		while(tokens[i].compareTo("player1") != 0){
    			Suit s = getSuitFromString(tokens[i]);
    			if(s == null){
    				return new State();
    			}
    			i++;
    			Rank r = getRankFromString(tokens[i]);
    			if(r == null){
    				System.out.println("Here5");
    				return new State();
    			}
    			i++;
    			Card c = new Card(s, r);
    			deck.addOneCard(c);
    		}
    		i++;
    		// Getting player 1's hand data
    		Player p1 = new Player(State.PLAYER1);
    		while(tokens[i].compareTo("inTableP1")!=0){
    			Suit s = getSuitFromString(tokens[i]);
    			if(s == null){
    				return new State();
    			}
    			i++;
    			Rank r = getRankFromString(tokens[i]);
    			if(r == null){
    				return new State();
    			}
    			i++;
    			Card c = new Card(s, r);
    			p1.getHand().addOneCard(c);
    		}
    		// Move head.
    		while(tokens[i].compareTo("inTableP1")!=0){
    			i++;
    		}
    		i++;
    		// Getting player 1's cardsInTable
    		while(tokens[i].compareTo("player2")!=0){
    			Rank r = getRankFromString(tokens[i]);
    			if(r == null){
    				return new State();
    			}
    			i++;
    			p1.getPlayerFourRankCardsPlacedInTable().add(r);
    		}
    		i++;
    		// Getting player 2's hand data
    		Player p2 = new Player(State.PLAYER2);
    		while(tokens[i].compareTo("inTableP2")!=0){
    			Suit s = getSuitFromString(tokens[i]);
    			if(s == null){
    				return new State();
    			}
    			i++;
    			Rank r = getRankFromString(tokens[i]);
    			if(r == null){
    				return new State();
    			}
    			i++;
    			Card c = new Card(s, r);
    			p2.getHand().addOneCard(c);
    		}
    		i++;
    		// Getting player 2's cardsInTable
    		while(tokens[i].compareTo("gameResult")!=0){
    			Rank r = getRankFromString(tokens[i]);
    			if(r == null){
    				System.out.println("Here3");
    				return new State();
    			}
    			i++;
    			p2.getPlayerFourRankCardsPlacedInTable().add(r);
    		}
    		i++;
    		// Getting game's result.
    		GameResult gameResult = null;
    		if(tokens[i].compareTo("null")!=0){
    			int winner = Integer.parseInt(tokens[i]);
    			i++;
    			GameResultReason reason = getReasonFromString(tokens[i]);
    			gameResult = new GameResult(winner, reason);
    			i++;
    		}
    		ArrayList<Rank> cardsPlacedInTable = new ArrayList<Rank>();
    		for(Rank r :p1.getPlayerFourRankCardsPlacedInTable()){
    			cardsPlacedInTable.add(r);
    		}
    		for(Rank r :p2.getPlayerFourRankCardsPlacedInTable()){
    			cardsPlacedInTable.add(r);
    		}
    		return new State(turn, deck, p1, p2, gameResult, cardsPlacedInTable);
    	}
    	catch(StringIndexOutOfBoundsException e){
    		return new State();
    	}
    	catch(ArrayIndexOutOfBoundsException e){
    		return new State();
    	}
    	
    }
	
    /**
     * Creates an initial state of the game
     * @return a string representation
     */
    public static String initialState(){
    	int turn = State.PLAYER1; 
		Deck mainDeck = new Deck();
		Player p1 = new Player(State.PLAYER1); 
		Player p2 = new Player(State.PLAYER2); 
		ArrayList<Rank> groupOfCardsPlacedInTable = new ArrayList<Rank>();
		mainDeck.restoreDeck();
		IntGeneratorJP gen = new IntGeneratorJP();
		mainDeck.shuffle(gen);
		GameResult gameResult = null;
		State state = new State(turn, mainDeck, p1, p2, gameResult, groupOfCardsPlacedInTable);
		int numberCardsPerPlayer = 7;
		state.dealInitialHands(numberCardsPerPlayer);
		return serializeState(state);
    }
	
    /**
    * It finds the appropriate game result reason that matches the string token passed.
    * @param token
    * @return
    */
   private static GameResultReason getReasonFromString(String token){
   	GameResultReason rs = null;
   	GameResultReason [] vals = rs.values();
   	for(int i=0; i<vals.length;i++){
   		if(token.compareTo(vals[i].toString())==0){
   			rs = vals[i];
   		}
   	}
   	return rs;
   }
   
   /**
    * Finds the matching suit from the string passed.
    * @param suit
    * @return
    */
   private static Suit getSuitFromString(String suit){
		Suit s = null;
		Suit[] vals = s.values();
		for(int i=0; i<vals.length;i++){
			if(suit.compareTo(vals[i].toString())==0){
				s = vals[i];
			}
		}
   	return s;
   }
   /**
    * Finds the matching rank from the string passed.
    * @param rank
    * @return
    */
   public static Rank getRankFromString(String rank){
	rank = rank.trim();
   	Rank r = null;
   	Rank[] vals = Rank.values();
   	for(int i=0; i<vals.length; i++){
   		if(rank.compareTo(vals[i].toString())==0){
   			r = vals[i];
   		}
       }
   	return r;
   }

}
